package commons.params.param_component;

import commons.utils.Properties_INI;
import commons.params.A_ParamsManager;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JTextField;
import commons.utils.Utils;
import commons.utils.UtilsCalcul;
import java.awt.event.FocusListener;
import java.util.ArrayList;
import javax.swing.JCheckBox;

// Représente une valeur unique type TextField ou ComboBox
// Si comboBox, il faut fournir une liste de choix, et le combo est forcé à editable
//  pour pouvoir afficher une valeur non permise mais stockée dans un fichier de config lu sur disque
// Peut représenter une valeur de type couleur, dans ce cas, le JComponent doit être un JButton

public class ParamComponent_simple extends AbstractParamComponent implements ActionListener, FocusListener, I_Activator {

	public enum TypeParam {TYPE_INT, TYPE_FLOAT, TYPE_STRING, TYPE_BOOL, TYPE_COLOR};
	public enum Condition {POSITIVE, POSITIVE_STRICT, BETWEEN_INCLUSIVE, BETWEEN_EXCLUSIVE};

	private String value; //  si TYPE_COLOR, value = entier (forme RGB) codant la couleur, si boolean = "YES" ou "NO"
	private TypeParam typeParam;
	private Condition condition; // pour les type INT ou FLOAT
	private double minValue, maxValue;
	private ArrayList<String> possibleValues;

	// Versions light
	public ParamComponent_simple(String name, A_ParamsManager paramsManager, JComponent component, TypeParam typeParam, boolean bCanBeNull) {
		this(name, paramsManager, component, typeParam, bCanBeNull, null, null, true);
	}
	public ParamComponent_simple(String name, A_ParamsManager paramsManager, JComponent component, TypeParam typeParam, boolean bCanBeNull, boolean bCaseSensitive) {
		this(name, paramsManager, component, typeParam, bCanBeNull, null, null, bCaseSensitive);
	}
	public ParamComponent_simple(String name, A_ParamsManager paramsManager, JComponent component, TypeParam typeParam, boolean bCanBeNull, Condition condition) {
		this(name, paramsManager, component, typeParam, bCanBeNull, condition, null, true);
		assert(condition != Condition.BETWEEN_INCLUSIVE && condition != Condition.BETWEEN_EXCLUSIVE);
	}
	public ParamComponent_simple(String name, A_ParamsManager paramsManager, JComponent component, TypeParam typeParam, boolean bCanBeNull, Condition condition, double minValue, double maxValue) {
		this(name, paramsManager, component, typeParam, bCanBeNull, condition, null, true);
		assert(condition == Condition.BETWEEN_INCLUSIVE || condition == Condition.BETWEEN_EXCLUSIVE);
		this.minValue = minValue;
		this.maxValue = maxValue;
	}
	public ParamComponent_simple(String name, A_ParamsManager paramsManager, JComponent component, TypeParam typeParam, boolean bCanBeNull, Condition condition, double minValue, double maxValue, I_Activator activator) {
		this(name, paramsManager, component, typeParam, bCanBeNull, condition, activator, true);
		assert(condition == Condition.BETWEEN_INCLUSIVE || condition == Condition.BETWEEN_EXCLUSIVE);
		this.minValue = minValue;
		this.maxValue = maxValue;
	}
	public ParamComponent_simple(String name, A_ParamsManager paramsManager, JComponent component, TypeParam typeParam, boolean bCanBeNull, I_Activator activator) {
		this(name, paramsManager, component, typeParam, bCanBeNull, null, activator, true);
	}
	public ParamComponent_simple(String name, A_ParamsManager paramsManager, JComponent component, TypeParam typeParam, boolean bCanBeNull, Condition condition, I_Activator activator) {
		this(name, paramsManager, component, typeParam, bCanBeNull, condition, activator, true);
	}
	// Version presque complète
	public ParamComponent_simple(String name, A_ParamsManager paramsManager, JComponent component, TypeParam typeParam, boolean bCanBeNull, Condition condition, I_Activator activator, boolean bCaseSensitive) {
		super(name, paramsManager, component, bCanBeNull, activator, bCaseSensitive);
		this.typeParam = typeParam;
		this.condition = condition;

		value = "";

		assert (component instanceof JTextField) ||
				(component instanceof JComboBox) ||
				(component instanceof JCheckBox) ||
				(component instanceof JButton): "Type de component non pris en charge";

		// Pour le TYPE_BOOL, il faut un JCheckBox
		assert !(component instanceof JCheckBox) || typeParam==TypeParam.TYPE_BOOL;

		// Pour le TYPE_COLOR, il faut un JButton
		assert !(component instanceof JButton) || typeParam==TypeParam.TYPE_COLOR;

		// Dans le cas d'un combo, on force son caractère editable
		if (component instanceof JComboBox)
			((JComboBox)component).setEditable(true);

		// Cas special des CheckBox, il faut initialier 'value'
		if (component instanceof JCheckBox) {
			this.bCanBeNull = false;
			if (((JCheckBox)component).isSelected())
				value = "YES";
			else
				value = "NO";
		}

		// On demande au component d'appeler une méthode spéciale du ParamManager
		//  lors de chaque modif du contenu
		if (component instanceof JTextField) {
			((JTextField)component).addActionListener(this);
			//((JTextField)component).getDocument().addDocumentListener(this);
			((JTextField)component).addFocusListener(this);
		}
		else if (component instanceof JCheckBox)
			((JCheckBox)component).addActionListener(this);
		else if (component instanceof JComboBox)
			((JComboBox)component).addActionListener(this);
		else if (component instanceof JButton)
			((JButton)component).addActionListener(this);
	}

	// Appelée par le component en cas de modif utilisateur
	// Prévient le ParamManager à la moindre modif utilisateur dans le component
	public void actionPerformed(ActionEvent e) {
		//  Dans le cas des comboBox éditables, ce filtre evite 2 appels au traitement des modif
		//  lors de l'édition manuelle d'une valeur par l'utilisateur
		if (component instanceof JComboBox && e.getActionCommand().equals("comboBoxEdited"))
			return;

		// Cas spécial des JButton pour le TYPE_COLOR : on lance une dialog de choix de couleur
		if (typeParam == TypeParam.TYPE_COLOR) {
			//System.out.println("BOUTON ------ > " + e.getActionCommand());
			Color newColor = JColorChooser.showDialog(paramsManager.dlg, "Choisir une couleur", getValue_color());
			if (newColor == null)
				// Annulation du choix de couleur par l'utilisateur
				return;
			else
				// On stocke la couleur dès à présent
				// Il n'y aura donc rien a faire dans 'readFromComponent()'
				value = "" + newColor.getRGB();
		}

		paramsManager.traiteOneModifUtilisateur();
	}


	public void focusGained(FocusEvent e) {

	}

	// Si un text field perd le focus, il faut retester : on valide soit par enter soit en passant à un autre component
	// Notamment, si en cours d'édition, on appuie sur le bouton GO, il faut faire une passe de test avant de sortir !!
	public void focusLost(FocusEvent e) {
		if (component instanceof JTextField)
			paramsManager.traiteOneModifUtilisateur();
	}


	// A initialiser avant tout appel à 'writeToComponent()'
	@Override
	public void setPossibleValues (ArrayList<String> possibleValues) {
		assert (component instanceof JComboBox);
		this.possibleValues = possibleValues;
	}

	//**************************************************************************
	//**************************************************************************

	@Override
	public Object getValue() {
		return value;
	}

	// GetValue spécifique, à utiliser selon le type de la valeur
	public String getValue_string() {
		return value;
	}

	public int getValue_int() {
		assert (typeParam==TypeParam.TYPE_INT);
		// on doit tester le type ici, malgré les tests standard automatiques
		//  car dans le cas d'un paramètre manquant, mais non activé car son activator est décoché
		//  aucun test n'est réalisé et n'oblige l'utilisateur à entrer une valeur du bon type !
		if (Utils.isInteger(value))
			return Integer.parseInt(value);
		else
			return 0;
	}

	public double getValue_double() {
		assert (typeParam==TypeParam.TYPE_FLOAT || typeParam==TypeParam.TYPE_INT);

		// on doit tester le type ici, malgré les tests standard automatiques
		//  car dans le cas d'un paramètre manquant, mais non activé car son activator est décoché
		//  aucun test n'est réalisé et n'oblige l'utilisateur à entrer une valeur du bon type !
		if (Utils.isFloat(value))
			return Double.parseDouble(value);
		else
			return 0.0;
	}

	public boolean getValue_bool() {
		assert (typeParam==TypeParam.TYPE_BOOL);
		return value.equals("YES") ? true : false;
	}
	// Renvoit un Color à partir de la valeur encodée dans 'value'
	public Color getValue_color() {
		assert (typeParam==TypeParam.TYPE_COLOR);
		if (value == null || value.trim().length()==0) return null;

		try{
			int iColor = Integer.parseInt(value);
			return new Color(iColor);
		} catch (Exception e) {
			return null;
		}
	}


	public boolean isActivate() {
		return getValue_bool();
	}

	
	@Override
	public void makeStandardControls() {
		// pré-test si le component a un activator desactivé (active ou non le component en fonction)
		if (!needControl()) return;

		// test nullité de la valeur
		if (!bCanBeNull && bIsNull) {
			invalidateComponent("Paramètre obligatoire", true);
			return;
		}
		if (bCanBeNull && bIsNull) {
			return;
		}

		// Si une liste de valeurs possibles a été fournie, la valeur doit en faire partie
		if (possibleValues != null) {
			if (!isValueInValeurPossible())
				invalidateComponent("Choisir une valeur de la liste de choix", true);

		} else {

			// Sinon, (entrée libre de l'utilisateur) : test si la valeur correspond au type attendu
			//  et à l'éventuelle condition
			switch (typeParam) {
				case TYPE_INT:
					if (!Utils.isInteger(value))
						invalidateComponent("Entrer une valeur de type entier", true);
					else if (condition!=null) {
						int val = Integer.parseInt(value);
						switch (condition) {
							case POSITIVE:
								if (val<0) invalidateComponent("La valeur doit être >= 0", true);
								break;
							case POSITIVE_STRICT:
								if (val<=0)	invalidateComponent("La valeur doit être > 0 (strict)", true);
								break;
							case BETWEEN_INCLUSIVE:
								if (val<minValue || val>maxValue) invalidateComponent("La valeur doit être comprise dans l'intervalle [" + minValue + " , " + maxValue + "]" , true);
								break;
							case BETWEEN_EXCLUSIVE:
								if (val<=minValue || val>=maxValue) invalidateComponent("La valeur doit être comprise dans l'intervalle ]" + minValue + " , " + maxValue + "[" , true);
								break;
							default:
								assert(false);
						}
					}
					break;

				case TYPE_FLOAT:
					if (!Utils.isFloat(value))
						invalidateComponent("Entrer une valeur de type réel", true);
					else if (condition!=null) {
						double val = Double.parseDouble(value);
						switch (condition) {
							case POSITIVE:
								if (val<0) invalidateComponent("La valeur doit être >= 0", true);
								break;
							case POSITIVE_STRICT:
								if (val<=0) invalidateComponent("La valeur doit être > 0 (strict)", true);
								break;
							case BETWEEN_INCLUSIVE:
								if (val<minValue || val>maxValue) invalidateComponent("La valeur doit être comprise dans l'intervalle [" + minValue + " , " + maxValue + "]" , true);
								break;
							case BETWEEN_EXCLUSIVE:
								if (val<=minValue || val>=maxValue) invalidateComponent("La valeur doit être comprise dans l'intervalle ]" + minValue + " , " + maxValue + "[" , true);
								break;
							default:
								assert(false);
						}
					}
					break;

				case TYPE_COLOR:
					if(!Utils.isInteger(value))
						invalidateComponent("Choisir une couleur", true);
					break;

				case TYPE_STRING:
					// aucun test spécifique
					break;

				case TYPE_BOOL:
					// aucun test spécifique
					break;

				default:
					assert(false);
			}
		}
	}

	private boolean isValueInValeurPossible() {
		for (String val : possibleValues) {
			if (bCaseSensitive) {
				if (val.equals(value))
					return true;
			} else {
				if (val.toLowerCase().equals(value.toLowerCase()))
					return true;
			}
		}
		return false;
	}

	
	@Override
	public void readFromComponent() {
		if (component instanceof JTextField)
			value = ((JTextField)component).getText();
		else if (component instanceof JCheckBox) {
			if (((JCheckBox)component).isSelected())
				value = "YES";
			else
				value = "NO";
		} else if (component instanceof JComboBox) {
			if (((JComboBox)component).getSelectedItem() != null)
				value = ((JComboBox)component).getSelectedItem().toString();
			else
				value = "";
		} else if (component instanceof JButton) {
			// Cas du TYPE_COLOR
			// Rien a faire, car la valeur de la couleur a été lue lors du traitement de l'appui sur le bouton
		} else
			assert false : "Type de component non pris en charge";

		bIsNull = (value==null || value.length()==0);
	}

	@Override
	public void writeToComponent() {
		if (component instanceof JTextField)
			((JTextField)component).setText(value);
		
		else if (component instanceof JCheckBox)
			((JCheckBox)component).setSelected(value.equals("YES"));

		else if (component instanceof JComboBox) {

			// Si combobox, on re-initialise a chaque fois avec la liste fournie
			//  'possibleValues' en effet, celle-ci a public changer selon
			//  les éléments des autres component de la Dialog
			assert (possibleValues!=null);
			((JComboBox)component).removeAllItems();
			for (String val : possibleValues)
				((JComboBox)component).addItem(val);

			((JComboBox)component).setSelectedItem(value);

		} else if (component instanceof JButton) {
			// On est dans le TYPE_COLOR
			Color color = getValue_color();
			if (color != null) {
				((JButton)component).setBackground(color);
				((JButton)component).setText("R:" + color.getRed() + " G:" + color.getGreen() + " B:" + color.getBlue());
				// Suivant la couleur du fond, on met le texte en noir ou en blanc
				if ((color.getRed() + color.getGreen() + color.getBlue())/3 > 125)
					((JButton)component).setForeground(Color.BLACK);
				else
					((JButton)component).setForeground(Color.WHITE);

			} else {
				((JButton)component).setBackground(javax.swing.UIManager.getDefaults().getColor("Button.background"));
				((JButton)component).setForeground(paramsManager.dlg.COLOR_INVALID);
				((JButton)component).setText("?");
			}
		}

		else
			assert false : "Type de component non pris en charge";
	}

	@Override
	// Renvoie "" si ok , msg d'erreur sinon
	public boolean loadFromProperties(Properties_INI props) {
		
		if (!isThereProperty(props, name)) {
			// cas particulier des types bool : on rale (return false), mais on l'initialise qd meme à "NO"
			// comme ca, il ne sera pas en rouge, sinon ca obligerait à l'utilisateur souhaitant le laisser
			// decocher, a le cocher puis le decocher pour enlever le rouge ...
			if (typeParam==TypeParam.TYPE_BOOL) {
				value = "NO";
				bIsNull = false;
			} else {
				value = "";
				bIsNull = true;
			}

			return false;
		}

		value = readProperty (props, name);
		// Pour l'instant, on prend la valeur sous forme de chaine
		// On testera si la valeur correspond au type attendu au moment du
		//  makeStandardControls(), juste avant l'écriture de la valeur dans le component

		bIsNull = (value==null || value.length()==0);
		return true;
	}


	@Override
	public void storeInProperties(Properties_INI props) {
		props.setProperty(name, value);
	}

	public void setValue(Object val) {
		if (val == null)
			this.value = "";
		else {
			assert (val instanceof String);
			this.value = (String) val;
		}
		
		bIsNull = (value==null || value.length()==0);
	}

}
